package com.blue.integration.spring.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.params.SetParams;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class RedisCache
{
	private static Logger logger = LoggerFactory.getLogger(RedisCache.class);

	private static final Random RANDOM = new Random();
	private static final int EXPIRE = 600; // redis 锁过期时间，秒
	private static final int WAIT_FOR_LOCK = 100; // 检查锁间隔时间，毫秒
	private static final int DEFAULT_TIMEOUNT = 5000; // 默认锁时间，毫秒
	public static final String NX = "NX";
	public static final String EX = "EX";
	public static final String OK = "OK";
	public static final String UNLOCK_LUA;

	@Autowired(required = false)
	@Qualifier("redisTemplate")
	protected RedisTemplate<String, Object> redisTemplate;

	static
	{
		StringBuilder sb = new StringBuilder();
		sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
		sb.append("then ");
		sb.append("    return redis.call(\"del\",KEYS[1]) ");
		sb.append("else ");
		sb.append("    return 0 ");
		sb.append("end ");
		UNLOCK_LUA = sb.toString();
	}

	public RedisCache()
	{
	}

	/**
	 * 设置缓存
	 *
	 * @param key 键
	 * @param obj 值
	 */
	public void set(String key, Object obj)
	{
		this.set(key, obj, 1, TimeUnit.HOURS);
	}

	/**
	 * 设置缓存
	 * 
	 * @param key 键
	 * @param obj 值
	 * @param n 数量
	 * @param unit 时间单位
	 */
	public void set(String key, Object obj, long n, TimeUnit unit)
	{
		if (redisTemplate == null || key == null || key.isEmpty() || obj == null || unit == null)
			return;

		long expire = unit.toMillis(n) + this.random();
		if (n > 0)
		{
			redisTemplate.boundValueOps(key).set(obj, expire, TimeUnit.MILLISECONDS);
		}
		else
		{
			redisTemplate.boundValueOps(key).set(obj);
			redisTemplate.persist(key);
		}
		logger.debug("更新缓存 key={}", key);
	}

	/**
	 * 获取缓存
	 * 
	 * @param <T> 类型参数
	 * @param key 键
	 * @return 缓存对象
	 */
	@SuppressWarnings("unchecked")
	public <T> T get(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return null;

		T obj = (T)redisTemplate.boundValueOps(key).get();
		logger.debug("命中缓存 key={}，{}", key, obj != null);
		return obj;
	}

	/**
	 * 获取对象过期时间，单位：秒
	 * 
	 * @param key 键
	 * @return 获取对象还有多少秒过期，-1表示不存在
	 */
	public int expire(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return -1;

		Long expire = redisTemplate.getExpire(key, TimeUnit.SECONDS);
		return expire == null ? -1 : expire.intValue();
	}

	/**
	 * 设置对象过期时间
	 *
	 * @param key 键
	 * @param n 数量
	 * @param unit 时间单位
	 */
	public void expire(String key, long n, TimeUnit unit)
	{
		if (redisTemplate == null || key == null || key.isEmpty() || unit == null)
			return;

		long expire = unit.toMillis(n) + this.random();
		if (n > 0)
		{
			redisTemplate.expire(key, expire, TimeUnit.MILLISECONDS);
		}
		else
		{
			redisTemplate.persist(key);
		}
	}

	/**
	 * 判断键是否存在
	 *
	 * @param key 键
	 * @return 存在返回 true，不存在返回 false
	 */
	public boolean exist(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return false;

		return redisTemplate.hasKey(key);
	}

	/**
	 * 删除缓存
	 * 
	 * @param key 键
	 */
	public void delete(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return;

		redisTemplate.delete(key);
	}

	/**
	 * 比较是否相等
	 * 
	 * @param key 键
	 * @param obj 值
	 * @return
	 */
	public boolean equals(String key, Object obj)
	{
		if (redisTemplate == null || key == null || key.isEmpty() || obj == null)
			return false;

		Object o = redisTemplate.boundValueOps(key).get();
		return obj.equals(o);
	}

	private int random()
	{
		return RANDOM.nextInt(50);
	}

	/**
	 * 对key加锁
	 * 
	 * @param key
	 * @param timeout 超时，单位毫秒
	 * @return
	 */
	public boolean lock(String key, int timeout)
	{
		String lockKey = key + "~lock";

		long now = System.currentTimeMillis();
		try
		{
			while ((System.currentTimeMillis() - now) < timeout)
			{
				if (OK.equalsIgnoreCase(this.setLock(lockKey, lockKey, EXPIRE)))
				{
					return true;
				}

				Thread.sleep(WAIT_FOR_LOCK + this.random());
			}
		}
		catch (Exception e)
		{
			logger.error("错误：" + e.getMessage(), e);
			redisTemplate.delete(lockKey);
		}
		return false;
	}

	/**
	 * 对key加锁，默认超时5秒
	 * 
	 * @param key
	 * @return
	 */
	public boolean lock(String key)
	{
		return this.lock(key, DEFAULT_TIMEOUNT);
	}

	/**
	 * 对key解锁
	 * 
	 * @param key
	 */
	public boolean unlock(String key)
	{
		final String lockKey = key + "~lock";

		return redisTemplate.execute(new RedisCallback<Boolean>()
		{
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException
			{
				Object nativeConnection = connection.getNativeConnection();
				Long result = 0L;
				
				List<String> keys = new ArrayList<>();
                keys.add(lockKey);
				
				if (nativeConnection instanceof JedisCluster) // 集群模式
				{
					result = (Long) ((JedisCluster)nativeConnection).eval(UNLOCK_LUA, keys, keys);
				}
				if (nativeConnection instanceof Jedis) // 单机模式
				{
					result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, keys);
				}
				if (result == 0)
				{
					logger.info("Redis分布式锁，解锁{}失败！解锁时间：{}",  lockKey, System.currentTimeMillis());
				} 
				return result == 1;

			}
		});
	}

	/**
	 * 判断key是否加锁
	 * 
	 * @param key
	 * @return true为加锁，false为解锁
	 */
	public boolean isLock(String key)
	{
		String lockKey = key + "~lock";
		return redisTemplate.hasKey(lockKey);
	}

	private String setLock(final String key, final String value, final int seconds)
	{
		return redisTemplate.execute(new RedisCallback<String>()
		{
			public String doInRedis(RedisConnection connection) throws DataAccessException
			{
				Object nativeConnection = connection.getNativeConnection();
				String result = null;

				if (nativeConnection instanceof JedisCluster) // 集群模式
				{
					SetParams params = SetParams.setParams().nx().ex(seconds);
					result = ((JedisCluster)nativeConnection).set(key, value, params);
				}
				if (nativeConnection instanceof Jedis) // 单机模式
				{
					SetParams params = SetParams.setParams().nx().ex(seconds);
					result = ((Jedis) nativeConnection).set(key, value, params);
				}
				if (result != null && !result.isEmpty())
				{
					logger.info("获取锁 {} 的时间：{}",  key, System.currentTimeMillis());
				}
				return result;
			}
		});
	}

	/**
	 * 获取Hash操作类
	 *
	 * @param key 键
	 * @return Hash操作类
	 */
	public BoundHashOperations<String, String, Object> hashOps(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return null;

		return redisTemplate.boundHashOps(key);
	}

	/**
	 * 获取Set操作类
	 *
	 * @param key 键
	 * @return Set操作类
	 */
	public BoundSetOperations<String, Object> setOps(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return null;

		return redisTemplate.boundSetOps(key);
	}

	/**
	 * 获取List操作类
	 *
	 * @param key 键
	 * @return List操作类
	 */
	public BoundListOperations<String, Object> listOps(String key)
	{
		if (redisTemplate == null || key == null || key.isEmpty())
			return null;

		return redisTemplate.boundListOps(key);
	}

}
